/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.mvc;

import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Simple {@code Controller} implementation that transforms the virtual
 * path of a URL into a view name and returns that view.
 *
 * <p>Can optionally prepend a {@link #setPrefix prefix} and/or append a
 * {@link #setSuffix suffix} to build the viewname from the URL filename.
 *
 * <p>Find some examples below:
 * <ol>
 * <li>{@code "/index" -> "index"}</li>
 * <li>{@code "/index.html" -> "index"}</li>
 * <li>{@code "/index.html"} + prefix {@code "pre_"} and suffix {@code "_suf" -> "pre_index_suf"}</li>
 * <li>{@code "/products/view.html" -> "products/view"}</li>
 * </ol>
 *
 * <p>Thanks to David Barri for suggesting prefix/suffix support!
 *
 * @author Alef Arendsen
 * @author Juergen Hoeller
 * @author Rob Harrop
 * @see #setPrefix
 * @see #setSuffix
 */
public class UrlFilenameViewController extends AbstractUrlViewController {

    /**
     * Request URL path String to view name String.
     */
    private final Map<String, String> viewNameCache = new ConcurrentHashMap<>(256);
    private String prefix = "";
    private String suffix = "";

    /**
     * Return the prefix to prepend to the request URL filename.
     */
    protected String getPrefix() {
        return this.prefix;
    }

    /**
     * Set the prefix to prepend to the request URL filename
     * to build a view name.
     */
    public void setPrefix(@Nullable String prefix) {
        this.prefix = (prefix != null ? prefix : "");
    }

    /**
     * Return the suffix to append to the request URL filename.
     */
    protected String getSuffix() {
        return this.suffix;
    }

    /**
     * Set the suffix to append to the request URL filename
     * to build a view name.
     */
    public void setSuffix(@Nullable String suffix) {
        this.suffix = (suffix != null ? suffix : "");
    }

    /**
     * Returns view name based on the URL filename,
     * with prefix/suffix applied when appropriate.
     *
     * @see #extractViewNameFromUrlPath
     * @see #setPrefix
     * @see #setSuffix
     */
    @Override
    protected String getViewNameForRequest(HttpServletRequest request) {
        String uri = extractOperableUrl(request);
        return getViewNameForUrlPath(uri);
    }

    /**
     * Extract a URL path from the given request,
     * suitable for view name extraction.
     *
     * @param request current HTTP request
     * @return the URL to use for view name extraction
     */
    protected String extractOperableUrl(HttpServletRequest request) {
        String urlPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
        if (!StringUtils.hasText(urlPath)) {
            urlPath = getUrlPathHelper().getLookupPathForRequest(request);
        }
        return urlPath;
    }

    /**
     * Returns view name based on the URL filename,
     * with prefix/suffix applied when appropriate.
     *
     * @param uri the request URI; for example {@code "/index.html"}
     * @return the extracted URI filename; for example {@code "index"}
     * @see #extractViewNameFromUrlPath
     * @see #postProcessViewName
     */
    protected String getViewNameForUrlPath(String uri) {
        String viewName = this.viewNameCache.get(uri);
        if (viewName == null) {
            viewName = extractViewNameFromUrlPath(uri);
            viewName = postProcessViewName(viewName);
            this.viewNameCache.put(uri, viewName);
        }
        return viewName;
    }

    /**
     * Extract the URL filename from the given request URI.
     *
     * @param uri the request URI; for example {@code "/index.html"}
     * @return the extracted URI filename; for example {@code "index"}
     */
    protected String extractViewNameFromUrlPath(String uri) {
        int start = (uri.charAt(0) == '/' ? 1 : 0);
        int lastIndex = uri.lastIndexOf('.');
        int end = (lastIndex < 0 ? uri.length() : lastIndex);
        return uri.substring(start, end);
    }

    /**
     * Build the full view name based on the given view name
     * as indicated by the URL path.
     * <p>The default implementation simply applies prefix and suffix.
     * This can be overridden, for example, to manipulate upper case
     * / lower case, etc.
     *
     * @param viewName the original view name, as indicated by the URL path
     * @return the full view name to use
     * @see #getPrefix()
     * @see #getSuffix()
     */
    protected String postProcessViewName(String viewName) {
        return getPrefix() + viewName + getSuffix();
    }

}
